package com.meta.platform.login.plus.wechat.proxy;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.meta.framework.common.enums.HttpMethod;
import com.meta.framework.common.utils.http.HttpUtils;
import com.meta.framework.common.utils.spring.SpringUtils;
import com.meta.framework.http.HttpUtil;
import com.meta.platform.file.model.FileInfo;
import com.meta.platform.file.service.UploadService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 一体化销售小程序，微信推送渠道
 *
 * @Title: OneSaleWeixinService
 * @Package: com.ihomefnt.dolly.weichat.OneSaleWeixinService
 * @Author: M.simple
 * @Remark: The modified content
 * @CreateDate: 2023/4/19 11:06
 * @Version: v2.0
 */
@Slf4j
@Service
public class MutipWeixinService {

	private static final Logger LOG = LoggerFactory.getLogger(MutipWeixinService.class);

	@Resource
	private StringRedisTemplate stringRedisTemplate;

	@Value(value = "${muti.mini.app.list}")
	private String wechatAppInfosStr;

	private List<WechatAppInfo> wechatAppInfos;

	@Resource
	private UploadService uploadService;

	@PostConstruct
	public void initWechatInfo() {
		wechatAppInfos = JSONObject.parseArray(wechatAppInfosStr, WechatAppInfo.class);
		log.info("wechatAppInfos info init, size:{}", wechatAppInfos.size());
	}

	public Map<String, Object> authOpenId(String appId, String loginCode) {

		WechatAppInfo wechatInfo = getWechatInfo(appId);
		return oauth2GetOpenId(appId, wechatInfo.getMiniSecret(), loginCode);
	}

	/**
	 * 微信小程序登录获取openid和session_key
	 *
	 * @param appid
	 * @param appsecret
	 * @param loginCode
	 * @return
	 */
	public Map<String, Object> oauth2GetOpenId(String appid, String appsecret, String loginCode) {

		HttpMethod method = null;
		Map<String, Object> result = new HashMap<>();
		try {
			// 获取access_token的url
			String accessurl = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret="
					+ appsecret + "&js_code=" + loginCode + "&grant_type=authorization_code";
			// 使用GET方法
			String access_token = HttpUtil.get(accessurl);
			JSONObject object = JSON.parseObject(access_token);
			if (null != object.getString("openid")) {
				result.put("openId", object.getString("openid"));
				result.put("sessionKey", object.getString("session_key"));
			}
			if (org.apache.commons.lang3.StringUtils.isNotBlank(object.getString("unionid"))) {
				result.put("unionid", object.getString("unionid"));
			}
		} catch (Exception e) {
			LOG.error("oauth2GetOpenid - oauth2GetOpenid Exception ", e);
		}

		return result;
	}


	/**
	 * 微信小程序登录获取accessToken
	 *
	 * @param appId
	 * @param code
	 * @return
	 */
	public String getPhoneNumber(String appId, String code) {
		WechatAppInfo wechatInfo = getWechatInfo(appId);
		String accessToken = getAccessToken(appId);
		try {

			for (int i = 0; i < 3; i++) {
				// 获取access_token的url
				String accessurl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
				Map<String, String> map = new HashMap<>();
				map.put("code", code);
				String result = HttpUtils.sendPost(accessurl, JSONObject.toJSONString(map));
				JSONObject object = JSON.parseObject(result);
				String errcode = object.get("errcode").toString();
				if ("0".equals(errcode)) {
					JSONObject phoneInfo = JSONObject.parseObject(JSONObject.toJSONString(object.get("phone_info")));
					return phoneInfo.get("purePhoneNumber").toString();
				} else if ("42001".equals(errcode)) {
					// token失效，强制刷新
					accessToken = setAccessToken(true, appId);
				}
			}

		} catch (Exception e) {
			LOG.error("oauth2GetOpenid Exception ", e);
		}
		return null;
	}

	/**
	 * 微信小程序登录获取accessToken
	 *
	 * @param appid
	 * @param appsecret
	 * @return
	 */
	public static Map<String, Object> oauth2GetToken(String appid, String appsecret) {


		Map<String, Object> result = null;
		try {
			// 获取access_token的url
			// 获取access_token的url
			String accessurl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret="
					+ appsecret;            // 使用GET方法
			String access_token = HttpUtil.get(accessurl);
			JSONObject object = JSON.parseObject(access_token);
			if (null != object.get("access_token")) {
				result = new HashMap<String, Object>();
				result.put("expiresIn", object.get("expires_in"));
				result.put("accessToken", object.get("access_token"));
			}
		} catch (Exception e) {
			LOG.error("oauth2GetOpenid Exception ", e);
		}
		return result;
	}

	public WechatAppInfo getWechatInfo(Long projectId) {

		if (CollectionUtils.isEmpty(wechatAppInfos)) {
			throw new RuntimeException("基础配置信息错误。");
		}
		Map<String, WechatAppInfo> wechatMap = wechatAppInfos.stream().collect(Collectors.groupingBy(WechatAppInfo::getBusinessKey,
				Collectors.collectingAndThen(
						Collectors.toList(),
						list -> list.get(0))
		));
		WechatAppInfo wechatAppInfo = wechatMap.get(projectId + "");
		if (Objects.isNull(wechatAppInfo)) {
			return wechatMap.get("default");
		}

		return wechatAppInfo;
	}

	public WechatAppInfo getWechatInfo(String appId) {

		if (CollectionUtils.isEmpty(wechatAppInfos)) {
			throw new RuntimeException("基础配置信息错误。");
		}
		Map<String, WechatAppInfo> wechatMap = wechatAppInfos.stream().collect(Collectors.groupingBy(WechatAppInfo::getMiniAppId,
				Collectors.collectingAndThen(
						Collectors.toList(),
						list -> list.get(0))
		));
		WechatAppInfo wechatAppInfo = wechatMap.get(appId + "");

		return wechatAppInfo;
	}

	private byte[] doPost(String url, Map<String, Object> map) {
		byte[] result = null;

		//使用HTTPPost方法访问获取二维码链接url
		HttpPost httpPost = new HttpPost(url);

		//创建http连接客户端
		CloseableHttpClient client = HttpClients.createDefault();

		//设置头响应类型
		httpPost.addHeader("Content-Type", "application/json");
		try {
			// 设置请求的参数
			JSONObject postData = new JSONObject();
			for (Map.Entry<String, Object> entry : map.entrySet()) {
				postData.put(entry.getKey(), entry.getValue());
			}
			httpPost.setEntity(new StringEntity(postData.toString(), "UTF-8"));
			HttpResponse response = client.execute(httpPost);
			HttpEntity entity = response.getEntity();
			result = EntityUtils.toByteArray(entity);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			httpPost.releaseConnection();
		}
		return result;
	}

	/**
	 * 发送订阅消息
	 */
	public boolean pushMessage(String appId, String openId, String templateId, String page, Map<String, Map<String, String>> data) {

		WechatAppInfo wechatInfo = getWechatInfo(appId);
		//https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN
		String token = getAccessToken(appId);
		Map<String, Object> params = new HashMap<>();
		params.put("access_token", token);
		params.put("touser", openId);
		params.put("template_id", templateId);
		params.put("page", page);
		params.put("data", data);
//        params.put("miniprogram_state", "trial"); // 体验版小程序
//        params.put("miniprogram_state", "formal"); // 正式版小程序
//        params.put("miniprogram_state", "developer"); // 开发版小程序

		params.put("miniprogram_state", wechatInfo.getMiniEnvVersion());

		for (int i = 0; i < 3; i++) {
			byte[] res = doPost("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + token, params);
			JSONObject resMap = JSON.parseObject(new String(res));
			log.info("send pubsub message, message:{}, openId:{}", resMap.toString(), openId);
			if (resMap.getInteger("errcode") == 40001) {
				// token失效，强制刷新
				token = setAccessToken(true, appId);
				LOG.error("sendSubscribeMsg : token失效" + JSON.toJSONString(params));
				continue;
			}

			if (resMap.getInteger("errcode") == 40003) {
				LOG.error("sendSubscribeMsg : touser字段openid为空或者不正确" + JSON.toJSONString(params));
				return false;
			}

			if (resMap.getInteger("errcode") == 43101) {
				LOG.error("sendSubscribeMsg : errmsg -> user refuse to accept the msg " + JSON.toJSONString(params));
				return false;
			}

			if (resMap.getInteger("errcode") == 47003) {
				LOG.error("sendSubscribeMsg : errmsg -> argument invalid! data.thing2.value invalid " + JSON.toJSONString(params));
				return false;
			}

			if (resMap.getInteger("errcode") == 0) {
				return true;
			}

		}

		return false;
	}

	private String getWeChatPickKey(String appId) {
		WechatAppInfo wechatInfo = getWechatInfo(appId);
		return "wechatPick:" + wechatInfo.getMiniAppId() + ":" + wechatInfo.getMiniSecret();
	}

	private String setAccessToken(boolean force, String appId) {

		WechatAppInfo wechatInfo = getWechatInfo(appId);

		String lock = "wechat_token_lock";
		String accessToken;

		// 是否强制刷新
		if (force) {
			stringRedisTemplate.delete(getWeChatPickKey(appId));
		}

		int i = 0;
		while (i < 10) {
			i++;

			accessToken = stringRedisTemplate.opsForValue().get(getWeChatPickKey(appId));
			if (accessToken != null) {
				return accessToken;
			}

			if (Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(lock, "1", 10, TimeUnit.SECONDS))) {
				// 二次判断
				accessToken = stringRedisTemplate.opsForValue().get(getWeChatPickKey(appId));
				if (accessToken != null) {
					return accessToken;
				}

				//获取accessToken
				Map<String, Object> stringObjectMap = MutipWeixinService.oauth2GetToken(wechatInfo.getMiniAppId(), wechatInfo.getMiniSecret());
				Integer expiresIn = (Integer) stringObjectMap.get("expiresIn");
				accessToken = String.valueOf(stringObjectMap.get("accessToken"));
				stringRedisTemplate.opsForValue().set(getWeChatPickKey(appId), accessToken, expiresIn - 100, TimeUnit.SECONDS);

				stringRedisTemplate.delete(lock);
				return accessToken;
			} else {
				try {
					Thread.sleep(50L);
				} catch (InterruptedException e) {
					e.printStackTrace();
					return null;
				}
			}
		}

		return null;
	}

	private String getAccessToken(String appId) {
		String wechatPickKey = getWeChatPickKey(appId);
		String accessToken = stringRedisTemplate.opsForValue().get(wechatPickKey);
		if (accessToken == null) {
			accessToken = setAccessToken(false, appId);
		}

		//accessToken = setAccessToken(true, appId);
		return accessToken;
	}

	/**
	 * 获取小程序图片
	 *
	 * @return
	 */
	public String generateWechatImage(String appId, String page, String sence) {

		WechatAppInfo wechatInfo = getWechatInfo(appId);
		try {
			Map<String, Object> paraMap = new HashMap();
			if (!StringUtils.isEmpty(page)) {
				paraMap.put("page", page);
			}
			//二维码携带参数 不超过32位
			if (!StringUtils.isEmpty(sence)) {
				paraMap.put("scene", sence);
			}
			paraMap.put("env_version", wechatInfo.getMiniEnvVersion());
			paraMap.put("check_path", false);
			LOG.info("start create qrcode wx;page:{},sense:{}", page, sence);
			String accessToken = getAccessToken(appId);

			for (int i = 0; i < 3; i++) {
				byte[] res = doPost("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken, paraMap);
				if (!JSON.isValid(new String(res, StandardCharsets.UTF_8))) {
					FileInfo upload = uploadService.upload(res, UUID.randomUUID().toString()+".jpg");
					return upload.getUrl();
				} else {
					String jsonStr = new String(res, StandardCharsets.UTF_8);
					JSONObject errorInfo = JSON.parseObject(jsonStr);
					LOG.info("wx error:" + jsonStr);
					if (errorInfo.get("errcode").toString().equals("40001")) {
						// token失效，强制刷新
						accessToken = setAccessToken(true, appId);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}
